/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.controller;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Laurent WOUTERS
 */
public class Graph extends Element implements xowl.gmi.view.diagram.Graph.Actions {
    protected xowl.gmi.model.cvssl.Graph definition;
    protected xowl.gmi.view.diagram.Graph widget;
    protected List<GraphNode> childrenNodes;
    protected List<GraphConnector> childrenConnectors;
    
    public xowl.gmi.view.diagram.Graph getVisual() { return widget; }
    public void addChild(GraphNode node) {
        childrenNodes.add(node);
        widget.addChild(node.getVisual());
    }
    public void addChild(GraphConnector connector) {
        childrenConnectors.add(connector);
        xowl.gmi.model.ast.ASTNode nodeOrigin = connector.getASTNode().getOriginChild();
        xowl.gmi.model.ast.ASTNode nodeTarget = connector.getASTNode().getTargetChild();
        GraphNode origin = null;
        GraphNode target = null;
        for (GraphNode child : childrenNodes) {
            if (child.getASTNode() == nodeOrigin)
                origin = child;
            if (child.getASTNode() == nodeTarget)
                target = child;
        }
        connector.setNodes(origin, target);
        origin.addConnector(connector);
        target.addConnector(connector);
        widget.addChild(connector.getVisual());
    }
    
    public Graph(Controller controller, Element parent, xowl.gmi.model.cvssl.Graph def) {
        super(controller, parent);
        this.definition = def;
        this.childrenNodes = new ArrayList<>();
        this.childrenConnectors = new ArrayList<>();
        this.widget = new xowl.gmi.view.diagram.Graph(this);
    }
    
    @Override public xowl.gmi.view.diagram.Widget getWidget() { return widget; }
    
    @Override protected int getContainedASTCount() { return childrenNodes.size() + childrenConnectors.size(); }
    @Override protected int getLeftSiblingsASTCount(Element divider) { return 0; }
    @Override protected boolean insertAST(xowl.gmi.model.ast.ASTNode node) {
        xowl.gmi.model.cvssl.Symbol symbol = node.getSymbol();
        if (symbol instanceof xowl.gmi.model.cvssl.Variable)
            return insertNewNode(node, (xowl.gmi.model.cvssl.Variable)symbol);
        else if (symbol instanceof xowl.gmi.model.cvssl.Connector)
            return insertNewConnector(node, (xowl.gmi.model.cvssl.Connector)symbol);
        return false;
    }
    @Override protected ASTRemovalStatus removeAST(xowl.gmi.model.ast.ASTNode node) {
        for (GraphNode child : childrenNodes) {
            if (child.getASTNode() == node) {
                child.decommission();
                childrenNodes.remove(child);
                widget.removeChild(child.getVisual());
                controller.onDiagramUpdated();
                return ASTRemovalStatus.Done;
            }
        }
        for (GraphConnector connector : childrenConnectors) {
            if (connector.getASTNode() == node) {
                connector.decommission();
                childrenConnectors.remove(connector);
                widget.removeChild(connector.getVisual());
                connector.getOrigin().removeConnector(connector);
                connector.getTarget().removeConnector(connector);
                controller.onDiagramUpdated();
                return ASTRemovalStatus.Done;
            }
        }
        return ASTRemovalStatus.NodeNotFound;
    }
    
    @Override public void saveTo(xowl.gmi.model.Diagram diagram) {
        for (GraphNode node : childrenNodes)
            node.saveTo(diagram);
        for (GraphConnector connector : childrenConnectors)
            connector.saveTo(diagram);
    }
    @Override public void decommission() {
        for (GraphNode node : childrenNodes)
            node.decommission();
        for (GraphConnector connector : childrenConnectors)
            connector.decommission();
    }
    
    @Override public boolean acceptFlavor(java.awt.datatransfer.DataFlavor flavor) {
        return (flavor == Controller.flavorVariable || flavor == Controller.flavorASTNode || flavor == Controller.flavorASTElementTO);
    }
    @Override public boolean acceptData(java.awt.datatransfer.DataFlavor flavor, Object data) {
        if (flavor == Controller.flavorVariable) {
            xowl.gmi.model.cvssl.Variable var = (xowl.gmi.model.cvssl.Variable)data;
            return acceptSymbol(var);
        } else if (flavor == Controller.flavorASTNode) {
            xowl.gmi.model.ast.ASTNode node = (xowl.gmi.model.ast.ASTNode)data;
            xowl.gmi.model.cvssl.Variable var = (xowl.gmi.model.cvssl.Variable)node.getSymbol();
            return acceptSymbol(var);
        } else if (flavor == Controller.flavorASTElementTO) {
            ASTElementTO to = (ASTElementTO)data;
            if (to.element instanceof GraphNode) {
                GraphNode node = (GraphNode)to.element;
                if (childrenNodes.contains(node))
                    return true;
            }
        }
        return false;
    }
    private boolean acceptSymbol(xowl.gmi.model.cvssl.Symbol symbol) {
        for (xowl.gmi.model.cvssl.Node gn : definition.getAllNodes())
            if (gn.getVariable() == symbol)
                return true;
        for (xowl.gmi.model.cvssl.Connector connector : definition.getAllConnectors())
            if (connector == symbol)
                return true;
        return false;
    }
    @Override public boolean drop(java.awt.datatransfer.DataFlavor flavor, Object data, java.awt.Point location) {
        if (flavor == Controller.flavorVariable) {
            xowl.gmi.model.cvssl.Variable var = (xowl.gmi.model.cvssl.Variable)data;
            actionAddVariable(var, location);
            return true;
        } else if (flavor == Controller.flavorASTNode) {
            xowl.gmi.model.ast.ASTNode node = (xowl.gmi.model.ast.ASTNode)data;
            actionAddNode(node, location);
            return true;
        } else if (flavor == Controller.flavorASTElementTO) {
            ASTElementTO to = (ASTElementTO)data;
            if (to.element instanceof GraphNode) {
                GraphNode node = (GraphNode)to.element;
                if (childrenNodes.contains(node))
                    actionMoveNode(node, new java.awt.Point(location.x - to.dragPoint.x, location.y - to.dragPoint.y));
            }
            return true;
        }
        return false;
    }
    
    public boolean insertNewNode(xowl.gmi.model.ast.ASTNode node, xowl.gmi.model.cvssl.Variable variable) {
        if (!acceptSymbol(variable))
            return false;
        xowl.gmi.model.cvssl.Node gn = null;
        for (xowl.gmi.model.cvssl.Node x : definition.getAllNodes())
            if (x.getVariable() == variable)
                gn = x;
        List<xowl.gmi.model.ast.ASTNode> list = new ArrayList<>(node.getAllChildren());
        ASTMatcher.sort(list, node);
        GraphNode child = new GraphNode(controller, this, gn, node);
        addChild(child);
        Element sub = ASTMatcher.buildControlTree(controller, child, variable.getDefinition(), list, true);
        child.setChild(sub);
        controller.onDiagramUpdated();
        return true;
    }
    public boolean insertNewConnector(xowl.gmi.model.ast.ASTNode node, xowl.gmi.model.cvssl.Connector connector) {
        if (!acceptSymbol(connector))
            return false;
        GraphConnector child = (GraphConnector)ASTMatcher.buildControlTree(controller, this, connector, node);
        addChild(child);
        controller.onDiagramUpdated();
        return true;
    }
    
    protected void actionAddVariable(xowl.gmi.model.cvssl.Variable var, java.awt.Point location) {
        xowl.lang.owl2.IRI childNode = controller.getRepository().getIRI(controller.getNewNodeIRI());
        xowl.lang.owl2.IRI childSymbol = var.getIRI();
        xowl.lang.owl2.IRI parentNode = getNearestAST().getASTNode().getIRI();
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagDropElem);
        action.addInner(controller.getCommandBuilder().create(childNode, childSymbol));
        action.addInner(controller.getCommandBuilder().attach(childSymbol, parentNode, childNode));
        action.addInner(new GraphNodeSetPosition(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), location));
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), childrenNodes.size(), childrenNodes.size()));
        controller.executeUIAction(action);
    }
    protected void actionAddNode(xowl.gmi.model.ast.ASTNode node, java.awt.Point location) {
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagDropElem);
        xowl.lang.owl2.IRI childSymbol = node.getSymbol().getIRI();
        xowl.lang.owl2.IRI parentNode = getNearestAST().getASTNode().getIRI();
        xowl.lang.owl2.IRI childNode = node.getIRI();
        action.addInner(controller.getCommandBuilder().attach(childSymbol, parentNode, childNode));
        action.addInner(new GraphNodeSetPosition(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), location));
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), childrenNodes.size(), childrenNodes.size()));
        controller.executeUIAction(action);
    }
    public void actionConnect(xowl.gmi.model.cvssl.Connector connector, GraphNodePort origin, GraphNodePort target) {
        xowl.lang.owl2.IRI parentNode = getNearestAST().getASTNode().getIRI();
        xowl.lang.owl2.IRI childNode = controller.getRepository().getIRI(controller.getNewNodeIRI());
        xowl.lang.owl2.IRI originNode = origin.getNode().getASTNode().getIRI();
        xowl.lang.owl2.IRI targetNode = target.getNode().getASTNode().getIRI();
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagDrawConnector);
        action.addInner(controller.getCommandBuilder().connect(connector.getIRI(), childNode, originNode, targetNode));
        action.addInner(new GraphConnectorSetPorts(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), origin.getPortID(), target.getPortID(), origin.getPortID(), target.getPortID()));
        controller.executeUIAction(action);
    }
    public void actionMoveNode(GraphNode node, java.awt.Point location) {
        xowl.lang.owl2.IRI parentNode = getNearestAST().getASTNode().getIRI();
        xowl.lang.owl2.IRI childNode = node.getASTNode().getIRI();
        GraphNodeMove action = new GraphNodeMove(controller.getRepository(), parentNode.getHasValue(), childNode.getHasValue(), node.getWidget().getPosition().toAWTPoint(), location);
        DiagramUIAction wrapper = new DiagramUIAction(controller.getLocale().diagMoveElem);
        wrapper.addInner(action);
        controller.executeUIAction(wrapper);
    }
    
    public void actionNodeLayerUp(GraphNode node) {
        if (childrenNodes.get(0) == node)
            return;
        int newLayer = node.getLayer() - 1;
        String parentNode = getNearestAST().getASTNode().getIRIString();
        String childNode1 = node.getASTNode().getIRIString();
        String childNode2 = childrenNodes.get(newLayer).getASTNode().getIRIString();
        
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagLayerUp);
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode1, newLayer + 1, newLayer));
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode2, newLayer, newLayer + 1));
        controller.executeUIAction(action);
    }
    public void actionNodeLayerDown(GraphNode node) {
        if (childrenNodes.get(childrenNodes.size()-1) == node)
            return;
        int newLayer = node.getLayer() + 1;
        String parentNode = getNearestAST().getASTNode().getIRIString();
        String childNode1 = node.getASTNode().getIRIString();
        String childNode2 = childrenNodes.get(newLayer).getASTNode().getIRIString();
        
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagLayerDown);
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode2, newLayer, newLayer - 1));
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode1, newLayer - 1, newLayer));
        controller.executeUIAction(action);
    }
    public void actionNodeLayerTop(GraphNode node) {
        if (childrenNodes.get(0) == node)
            return;
        int currentLayer = node.getLayer();
        String parentNode = getNearestAST().getASTNode().getIRIString();
        String childNode = node.getASTNode().getIRIString();
        
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagLayerTop);
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode, currentLayer, 0));
        for (int i=0; i!=currentLayer; i++) {
            String temp = childrenNodes.get(i).getASTNode().getIRIString();
            action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, temp, i, i + 1));
        }
        controller.executeUIAction(action);
    }
    public void actionNodeLayerBottom(GraphNode node) {
        if (childrenNodes.get(childrenNodes.size()-1) == node)
            return;
        int currentLayer = node.getLayer();
        String parentNode = getNearestAST().getASTNode().getIRIString();
        String childNode = node.getASTNode().getIRIString();
        DiagramUIAction action = new DiagramUIAction(controller.getLocale().diagLayerBottom);
        for (int i=currentLayer+1; i!=childrenNodes.size()-1; i++) {
            String temp = childrenNodes.get(i).getASTNode().getIRIString();
            action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, temp, i, i - 1));
        }
        action.addInner(new GraphNodeSetLayer(controller.getRepository(), parentNode, childNode, currentLayer, childrenNodes.size()-1));
        controller.executeUIAction(action);
    }
    
    public void onNodeChangedLayer(GraphNode node, int layer) {
        childrenNodes.remove(node);
        childrenNodes.add(layer, node);
        widget.changeLayer(node.getVisual(), layer);
        controller.onDiagramUpdated();
    }
}
